﻿/////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2009 Eric Bowen
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 
// associated documentation files (the "Software"), to deal in the Software without restriction, including 
// without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
// copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the 
// following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial 
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 
// LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
/////////////////////////////////////////////////////////////////////////////////////////////////////
// Author's blog: http://blog.scrappydog.com
// Updated source code available:  http://scrappydb.codeplex.com
/////////////////////////////////////////////////////////////////////////////////////////////////////

using System;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using NLog;
using ScrappyDB.Attributes;

namespace ScrappyDB.Utilities
{
    public static class EntityHelper
    {
        // Create a logger for use in this class
        private static readonly Logger log = LogManager.GetCurrentClassLogger();

        public static string GetEncodedItemNameValue<T>(T o) where T : class, new()
        {
            string idFieldName = GetItemNamePropertyName(o);

            PropertyInfo idProperty = o.GetType().GetProperty(idFieldName);

            //if (idProperty.PropertyType == typeof(DateTime))
            //    return Utilities.DbDateTime.ConvertDateTimeToIso8601((DateTime)idProperty.GetValue(o, null));

            //else use default ToString()
            //return idProperty.GetValue(o, null).ToString();

            var encodedValue = EncodeItemNameValue<T>(idProperty.GetValue(o, null));
            return encodedValue;
        }

        /// <summary>
        /// Finds the name of the property for supplied class that should be used as the SimpleDB "ItemName"
        /// aka the primary key
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="o"></param>
        /// <returns>Property name of the property that should be used as the ItemName</returns>
        public static string GetItemNamePropertyName<T>(T o) where T : class
        {
            PropertyInfo[] properties;

            if (o.GetType().Name == "RuntimeType")
            {
                throw new ArgumentException("You must pass an INSTANCE of a type to this method not a TYPE!");
            }
            else
            {
                properties = o.GetType().GetProperties();
            }

            //1. is there a property that has been flagged with the [ItemName] attribute?
            foreach (PropertyInfo p in properties)
            {
                foreach (object a in p.GetCustomAttributes(true))
                {
                    if (a is ItemNameAttribute || a is System.ComponentModel.DataAnnotations.KeyAttribute)
                        return p.Name;
                }
            }

            //2. is there a property that is named with the pattern "EntityNameId" or "EntityName_Id"?
            string entityName = GetEntityName(o);
            foreach (PropertyInfo p in properties)
            {
                string lowerName = p.Name.ToLower();
                if (lowerName.StartsWith(entityName.ToLower()) && lowerName.EndsWith("id"))
                {
                    return p.Name;
                }
            }

            //3. is there a property that is just named "Id"?
            foreach (PropertyInfo p in properties)
            {
                string lowerName = p.Name.ToLower();
                if (lowerName == "id")
                {
                    return p.Name;
                }
            }

            //bad juju
            throw new Exception("Could not identify an Id field");
        }

        public static string GetEntityName<T>(T o)
        {
            string entityName;
            var entityNameAttribute = (EntityNameAttribute)
                                      o.GetType().GetCustomAttributes(typeof(EntityNameAttribute), true)
                                          .FirstOrDefault();
            if (entityNameAttribute != null)
            {
                entityName = entityNameAttribute.EntityName;
                if (entityName == "@NONE")
                    entityName = string.Empty;
            }
            else
            {
                entityName = o.GetType().Name;
            }

            log.Debug("EntityName: " + entityName);
            return entityName;
        }

        public static string GetDomainName<T>(T o)
        {
            string domainName;
            var domainAttribute = (DomainNameAttribute)
                                  o.GetType().GetCustomAttributes(typeof(DomainNameAttribute), true)
                                      .FirstOrDefault();
            if (domainAttribute != null)
            {
                domainName = domainAttribute.DomainName;
            }
            else
            {
                domainName = o.GetType().Name;
            }

            if (domainName == "ScrappyDbCollection`1")
                throw new Exception("Invalid save: you probably called 'db.SaveChanges(collection)' try 'collection.SaveChanges()' instead. ");

            return domainName;
        }

        public static PropertyInfo GetItemNameProperty<T>(T o) where T : class
        {
            string itemNamePropertyName = GetItemNamePropertyName(o);
            return o.GetType().GetProperty(itemNamePropertyName);
        }

        public static void LoadItemNameProperty<T>(T o, string id) where T : class
        {
            PropertyInfo idProperty = GetItemNameProperty(o);

            if (idProperty.CanWrite)
            {
                if (idProperty.PropertyType == typeof(string))
                {
                    //no cast needed
                    idProperty.SetValue(o, id, null);
                    return;
                }

                if (idProperty.PropertyType == typeof(Guid))
                {
                    idProperty.SetValue(o, new Guid(id), null);
                    return;
                }

                if (idProperty.PropertyType == typeof(int))
                {
                    idProperty.SetValue(o, Utility.DecodeNumericInt32(id), null);
                    return;
                }

                if (idProperty.PropertyType == typeof(short))
                {
                    idProperty.SetValue(o, Utility.DecodeNumericInt16(id), null);
                    return;
                }


                //else try generic convert type                               
                idProperty.SetValue(o, Convert.ChangeType(id, idProperty.PropertyType), null);

            }
        }

        public static string EncodeItemNameValue<T>(object id) where T : class, new()
        {
            string encodedId = null;

            TypeSwitch.Do(id,
                          TypeSwitch.Case<short>(() =>
                                                 encodedId = EncodeNumericItemName<T>(id)),
                          TypeSwitch.Case<int>(() =>
                                               encodedId = EncodeNumericItemName<T>(id)),
                          TypeSwitch.Case<DateTime>(() =>
                                                    encodedId = DbDateTime.ConvertDateTimeToIso8601((DateTime)id)),
                          TypeSwitch.Case<Guid>(() =>
                                                encodedId = id.ToString()),
                          TypeSwitch.Default(() =>
                                             encodedId = id.ToString()));
            return encodedId;
        }

        private static string EncodeNumericItemName<T>(object id) where T : class, new()
        {
            var o = new T();
            var prop = Utilities.EntityHelper.GetItemNameProperty(o);
            if (prop.GetCustomAttributes(typeof(RawNumberAttribute), true).Length > 0)
                return id.ToString();

            string encodedId = null;
            TypeSwitch.Do(id,
              TypeSwitch.Case<short>(() =>
                                     encodedId = Utility.EncodeNumericInt16((short)id)),
              TypeSwitch.Case<int>(() =>
                                   encodedId = Utility.EncodeNumericInt32((int)id)),
              TypeSwitch.Default(() =>
              {
                  throw new ArgumentException();
              }));

            return encodedId;
        }
    }
}